/*
 * Copyright (c) 2021 Loongson Technology Corporation Limited (www.loongson.cn)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Opsycon AB, Sweden.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#ifndef _KERNEL
#define _KERNEL
#endif

#include <asm-offsets.h>
#include <asm/asm.h>
#include <asm/regdef.h>
#include <asm/loongarch.h>
#include <asm/addrspace.h>
#include <config.h>

#define PRINT_CSR(offset)	\
			PRINTSTR("\r\ncsr 0x");	\
			li.w	a0, offset;	\
			bl	hexserial64;	\
			PRINTSTR(" ->0x");	\
			csrrd	a0, offset;	\
			bl	hexserial64;	\
			PRINTSTR("\r\n");


/*
 * Register usage:
 *
 * tp($r2)		link versus load offset, used to relocate absolute adresses.
 * gp($r21) 	global data pointer
 */
 #define relc_reg	tp

	.globl	_start
	.globl	start
_start:
start:
#if defined(CONFIG_SPL) && !defined(CONFIG_SPL_BUILD)
	// now running in ram from spl.
	/* clear Vint cofigure */
	la		t1, _start
	li.d	t0, (0x7 << 16)
	csrxchg zero, t0, 0x4
	/* set ebase address */
	li.d	t2, 0x1000
	add.d	t0, t1, t2
	csrwr	t0, 0xc
	/* set TLB excption address */
	li.d	t0, TO_PHYS_MASK
	and		t0, t1, t0
	li.d	t2, 0x1000
	add.d	t0, t1, t2
	csrwr	t0, 0x88

	// PRINTSTR("\r\nUboot start ...\r\n");
	b		locate
#endif

	li.d	t0, (1 << (24 + 32))	//set CPUCFG 0 bit24
	csrxchg	t0, t0, 0xc0

	li.d	t0, (0xa << (16 + 32))	//set CPUCFG 0x13 bit16-23
	li.d	t1, (0xff << (16 + 32))
	csrxchg	t0, t1, 0xc9

	/* enable perf counter as cp0 counter */
	li.w	t0, (0x1 << 16)
	csrxchg t0, t0, 0x200

	/* open auto flush SFB */
	li.w	t0, (0x1 << 9)
	csrxchg t0, t0, 0x80

	/* fast_ldq_dis close */
	li.w	t0, (0x1 << 12)
	csrxchg zero, t0, 0x80

	/* low power setting */
	li.w	t0, 0xe
	csrxchg t0, t0, 0xf0

	li.d	t0, UNCACHED_MEMORY_ADDR | 0xf
	csrwr	t0, 0x180
	li.d	t0, CACHED_MEMORY_ADDR | 0x1f
	csrwr	t0, 0x181


/*
 * should before execution jr shutdown slave core
 * otherwise speculative execution cause error
 */
	/* clear Vint cofigure */
	li.d	t0, (0x7 << 16)
	csrxchg zero, t0, 0x4
	/* set ebase address */
	li.d	t0, PHYS_TO_CACHED(BOOT_SPACE_BASE + 0x1000)
	csrwr	t0, 0xc
	/* set TLB excption address */
	li.d	t0, BOOT_SPACE_BASE + 0x1000
	csrwr	t0, 0x88

	/* enable llexc */
	li.w	t0, (1 << 3)
	csrxchg zero, t0, 0xc1

	/* disable interrupt */
	li.d	t0, (1 << 2)
	csrxchg zero, t0, 0x0

	bl		lowlevel_init
	nop

	/* don`t change this code, jumping to cached address */
	li.d	t1, CACHED_MEMORY_ADDR
	bl	1f
1:
	addi.d	t0, ra, 12
	or	t0, t1, t0
	jirl	zero, t0, 0
	/* now pc run to 0x90xxxxxxxxxxxxxx */
	/* DA disable for 0x80xxxxxxxxxxxxxx and 0x90xxxxxxxxxxxx address can be used */
	li.w	t0, 0xb0
	csrwr	t0, 0x0

	/* calculate ASM stage print function relc_reg address */
	la		relc_reg, start
	li.d	a0, PHYS_TO_UNCACHED(BOOT_SPACE_BASE)

	/* if change locked cache address may need change the following code */
	sub.d	relc_reg, relc_reg, a0

	bl		init_serial
	nop

bsp_start:
	// PRINTSTR("\r\nLoongArch Initializing...\r\n")
	dbar 0
	ibar 0
	nop

	bl	locate
	nop

	/* all exception entry */
	.org 0x1000
	/* relc_reg in different stage should fixup */
	la		a0, start
	li.d	a1, PHYS_TO_UNCACHED(BOOT_SPACE_BASE)
	sub.d	a0, a0, a1
	li.d	a1, 0x00000000ffff0000
	and		a0, a0, a1
	beq		a0, relc_reg, 1f
	move	relc_reg, zero
1:
	and		relc_reg, relc_reg, a0

	csrrd	t0, 0x8a
	andi	t0, t0, 0x1
	bnez	t0, 2f

	PRINTSTR("\r\nCPU Trigger exception!\r\n")
	PRINT_CSR(0x4);
	PRINT_CSR(0x5);
	PRINT_CSR(0x6);
	PRINT_CSR(0x7);
	PRINT_CSR(0x8);
1:
	b	1b
2:
	li.d	t1, CACHED_MEMORY_ADDR
	bl	1f
1:
	addi.d	t0, ra, 12
	or		t0, t1, t0
	jirl	zero, t0, 0

	li.d	t0, 0xb0
	csrwr	t0, 0
	PRINTSTR("\r\nTLB exception!\r\n");
	PRINT_CSR(0x89);
	PRINT_CSR(0x8a);
1:
	b	1b

locate:
#if defined(CONFIG_SPL) && !defined(CONFIG_SPL_BUILD)
  #ifdef CONFIG_SYS_INIT_SP_ADDR
	li.d	a0, CONFIG_SYS_INIT_SP_ADDR
  #else
	li.d	a0, PHYS_TO_CACHED(MEM_WIN_BASE + 0x2000000)
  #endif
#else
	bl		ram_init
	// now the ram top addr is in reg a0
#endif

	or		sp, a0, zero
	bl		board_init_f_alloc_reserve
	bl		board_init_f_init_reserve

#ifdef CONFIG_DEBUG_UART
	la		t8, debug_uart_init
	jirl	ra, t8, 0
#endif

	// jump to uboot board_init_f.
	// PRINTSTR("\r\njump to board_init_f\r\n");
#if defined(CONFIG_SPL) && !defined(CONFIG_SPL_BUILD)
	li.d  	a0, 0x00800		// GD_FLG_SKIP_RELOC
#else
	li.d  	a0, 0
#endif
	la		t8, board_init_f
	jirl	ra, t8, 0

#ifdef CONFIG_SPL
	// jump to board_init_r
	ld.d	sp, gp, GD_START_ADDR_SP
	ld.d	a0, gp, GD_NEW_GD
	li.d  	a1, 0
	la		t8, board_init_r
	jirl	ra, t8, 0
#endif

	// Never get here!
	PRINTSTR("Fatal error! please reset the board!\r\n")
1:
	b 1b


/******************************************************
 *used: a0, t0 ~ t2
 ******************************************************/
LEAF(setup_stack_gd)
	or		s8, ra, zero
	li.d	t0, -16
	or		t1, a0, zero
	and		sp, t1, t0		/* force 16 byte alignment */
	li.d	t2, GD_SIZE
	sub.d	sp, sp, t2		/* reserve space for gd */
	and		sp, sp, t0		/* force 16 byte alignment */
	or		gp, sp, zero	/* save gd pointer */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
	li.d	t2, CONFIG_VAL(SYS_MALLOC_F_LEN)
	sub.d 	sp, sp, t2		/* reserve space for early malloc */
	and 	sp, sp, t0		/* force 16 byte alignment */
#endif
	or		fp, sp, zero

	/* Clear gd */
	or		t0, gp, zero
	li.d	t3, 0x0
1:
	st.d	zero, t0, 0
	addi.d	t0, t0, 8
	blt		t0, t1, 1b

#if CONFIG_VAL(SYS_MALLOC_F_LEN)
	st.d	sp, gp, GD_MALLOC_BASE	/* gd->malloc_base offset */
#endif

	or		ra, s8, zero
	jirl	zero, ra, 0
END(setup_stack_gd)

/******************************************************
 *used: a0~a4, relc_reg
 ******************************************************/
LEAF(printstr)
	or	a4, ra, zero
	sub.d	a3, a0, relc_reg
	ld.bu	a0, a3, 0
1:
	beqz	a0, 2f

	bl		printchar

	addi.d	a3, a3, 1
	ld.bu	a0, a3, 0
	b	1b

2:
	ori	ra, a4, 0
	jirl	zero, ra, 0
END(printstr)

/*****************************************************
 *used: a0~a5, relc_reg
 *****************************************************/
LEAF(hexserial)
	ori	a4, ra, 0
	ori	a3, a0, 0
	li.d	a5, 8
1:
	rotri.w a0, a3, 28
	or	a3, a0, zero
	andi	a0, a0, 0xf

	la	a1, hexchar
	sub.d	a1, a1, relc_reg

	add.d	a1, a1, a0
	ld.bu	a0, a1, 0

	bl		printchar

	addi.d	a5, a5, -1
	bnez	a5, 1b

	ori	ra, a4, 0
	jirl	zero, ra, 0
END(hexserial)


/*****************************************************
 *used: a0~a5, relc_reg
 *****************************************************/
LEAF(hexserial64)
	ori	a4, ra, 0
	ori	a3, a0, 0
	li.d	a5, 16
1:
	rotri.d a0, a3, 60
	or	a3, a0, zero
	andi	a0, a0, 0xf

	la	a1, hexchar
	sub.d	a1, a1, relc_reg

	add.d	a1, a1, a0
	ld.bu	a0, a1, 0

	bl		printchar

	addi.d	a5, a5, -1
	bnez	a5, 1b

	ori	ra, a4, 0
	jirl	zero, ra, 0
END(hexserial64)

	.section .rodata
hexchar:
	.ascii	"0123456789abcdef"
	.text
	.align	8
